﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Resources;
using System.Web.UI;
using PostSharp.Extensibility;
using PostSharp.CodeWeaver;
using PostSharp.Extensibility.Tasks;
using PostSharp.CodeModel;
using PostSharp.CodeModel.Helpers;
using PostSharp.Collections;

namespace PostSharp4ViewState.Weaver
{
   public sealed class PersistTask : Task, IAdviceProvider
   {            

      private readonly Dictionary<TypeDefDeclaration, PerTypeData> _perTypeData = new Dictionary<TypeDefDeclaration, PerTypeData>();

      internal ITypeSignature ObjectType { get; private set; }
      internal ITypeSignature ObjectArrayType { get; private set; }
      internal ITypeSignature VoidType { get; private set; }
      internal ITypeSignature BoolType { get; private set; }
      internal ITypeSignature EventArgsType { get; private set; }
      internal ITypeSignature ControlType { get; private set; }
      internal ITypeSignature PageType { get; private set; }

      protected override void Initialize()
      {
         ModuleDeclaration module = Project.Module;

         VoidType = module.FindType(typeof(void), BindingOptions.Default);
         ObjectType = module.FindType(typeof(object), BindingOptions.Default);
         ObjectArrayType = module.FindType(typeof(object[]), BindingOptions.Default);
         BoolType = module.FindType(typeof(bool), BindingOptions.Default);
         EventArgsType = module.FindType(typeof(EventArgs), BindingOptions.Default);
         ControlType = module.FindType(typeof(Control), BindingOptions.Default);
         PageType = module.FindType(typeof(Page), BindingOptions.Default);
      }

      #region IAdviceProvider Members
      public void ProvideAdvices(PostSharp.CodeWeaver.Weaver codeWeaver)
      {
         CustomAttributeDictionaryTask customAttributeDictionaryTask = CustomAttributeDictionaryTask.GetTask(Project);

         IEnumerator<ICustomAttributeInstance> customAttributeEnumerator = customAttributeDictionaryTask.GetCustomAttributesEnumerator(typeof(PersistAttribute), false);

         while (customAttributeEnumerator.MoveNext())
         {
            bool shouldWeave = false;
            TypeDefDeclaration wovenType = null;
            NamedDeclaration member = (NamedDeclaration)customAttributeEnumerator.Current.TargetElement;
            if (member.GetTokenType() == TokenType.FieldDef)
            {
               shouldWeave = true;
               wovenType = ((FieldDefDeclaration)member).DeclaringType;
            }
            else if (member.GetTokenType() == TokenType.Property)
            {               
               PropertyDeclaration propDef = (PropertyDeclaration)member;
               wovenType = propDef.DeclaringType;
               if (!propDef.CanRead)
               {
                  PS4VSMessageSource.Instance.Write(SeverityType.Warning, "PS4VS002", new object[] { propDef.Name, wovenType.Name });
               }
               else if (!propDef.CanWrite)
               {
                  PS4VSMessageSource.Instance.Write(SeverityType.Warning, "PS4VS003", new object[] { propDef.Name, wovenType.Name });
               }
               shouldWeave = propDef.CanRead && propDef.CanWrite;               
            }
            if (shouldWeave)
            {
               PersistAttribute attribute = (PersistAttribute)CustomAttributeHelper.ConstructRuntimeObject(customAttributeEnumerator.Current.Value, Project.Module);
               PerTypeData data;

               if (!_perTypeData.TryGetValue(wovenType, out data))
               {                  
                  if (!IsControlDescendant(wovenType))
                  {
                     PS4VSMessageSource.Instance.Write(SeverityType.Fatal, "PS4VS001", new object[] { wovenType.Name });
                  }
                  data = new PerTypeData();
                  _perTypeData.Add(wovenType, data);

                  LoadAdvice loadViewStateAdvice = new LoadAdvice(this, PersistMode.ViewState, data);
                  LoadAdvice loadControlStateAdvice = new LoadAdvice(this, PersistMode.ControlState, data);

                  SaveAdvice saveViewStateAdvice = new SaveAdvice(this, PersistMode.ViewState, data);
                  SaveAdvice saveControlStateAdvice = new SaveAdvice(this, PersistMode.ControlState, data);

                  AddAdvice(codeWeaver, wovenType, loadViewStateAdvice);
                  AddAdvice(codeWeaver, wovenType, loadControlStateAdvice);
                  AddAdvice(codeWeaver, wovenType, saveViewStateAdvice);
                  AddAdvice(codeWeaver, wovenType, saveControlStateAdvice);
               }
               if (attribute.Mode == PersistMode.ControlState && !data.RegisterLoadControlStateAdded)
               {
                  RegisterControlStateAdvice registerControlStateAdvice = new RegisterControlStateAdvice(this, null);
                  AddAdvice(codeWeaver, wovenType, registerControlStateAdvice);
                  data.RegisterLoadControlStateAdded = true;
               }
               data.AddMember(member, attribute.Mode);
            }
         }
      }
      #endregion

      private static void AddAdvice(PostSharp.CodeWeaver.Weaver codeWeaver, TypeDefDeclaration wovenType, PersistAdvice advice)
      {
         MethodDefDeclaration methodDef = wovenType.Methods.GetByName(advice.MethodName).FirstOrDefault();
         if (methodDef == null)
         {
            methodDef = new MethodDefDeclaration();
            wovenType.Methods.Add(methodDef);
            advice.CreateMethod(wovenType, methodDef);
            PostSharp.CodeWeaver.Weaver.IgnoreMethod(methodDef);

            codeWeaver.AddTypeLevelAdvice(advice, JoinPointKinds.BeforeStaticConstructor,
                                                     new Singleton<TypeDefDeclaration>(wovenType));
         }
         else
         {
            codeWeaver.AddMethodLevelAdvice(advice,
                                                     new Singleton<MethodDefDeclaration>(methodDef),
                                                     advice.JointPointKind,
                                                     null);
         }
      }

      private static bool IsControlDescendant(TypeDefDeclaration type)
      {
         TypeDefDeclaration currentType = type.BaseType.GetTypeDefinition();
         while (true)
         {
            if (currentType.Name == "System.Web.UI.Control")
            {
               return true;
            }
            if (currentType.BaseType != null)
            {
               currentType = currentType.BaseType.GetTypeDefinition();
            }
            else
            {
               return false;
            }
         }         
      }
   }
}
